#region Summary

// 
// Owner : JackieHan 
// 
// Solution : DailyCollection 
// 
// Project : Jackie.Utility.Compiler
// 
// File : CodeScanner.cs
// 
// CopyRight : Copyright © 2010-2011 Skomart Corporation, All Rights Reserved 
// 
// Summary : 词法分析器
// 
// History : 
//			2011-05-27 16:06:14	    Create 
// 

#endregion

#region Using

using System;
using System.Collections.Generic;
using Jackie.Utility.Compiler.Exception;

#endregion

namespace Jackie.Utility.Compiler.Scanner
{
    /// <summary> 
    /// 词法分析器
    /// </summary>
    public class CodeScanner
    {
        private const char Eof = '\0';

        private readonly KeyWord keyWordDefine;

        /// <summary>
        /// 代码
        /// </summary>
        private readonly string codeText;
        /// <summary>
        /// 开始读取位置
        /// </summary>
        private int startPosition = 0;
        /// <summary>
        /// 当前位置
        /// </summary>
        private int currentPosition = 0;

        /// <summary>
        /// 构造函数
        /// </summary>
        /// <param name="codeText">代码</param>
        public CodeScanner(string codeText)
        {
            this.codeText = codeText;
            keyWordDefine = new KeyWord();
        }

        private char ReadChar()
        {
            if (codeText == null || currentPosition >= codeText.Length)
                return Eof;
            return codeText[currentPosition];
        }

        private string GetReadedChars()
        {
            if (currentPosition >= codeText.Length)
                return codeText;
            return codeText.Substring(startPosition, currentPosition - startPosition + 1);
        }

        private string GetReadedCharsNotContainCurrent()
        {
            return codeText.Substring(startPosition, currentPosition - startPosition);
        }

        public Token GetNextToken()
        {
            Token currentToken = null;

            var state = State.Start;
            startPosition = currentPosition;

            while (state != State.Done)
            {
                var currentChar = ReadChar();
                switch (state)
                {
                    case State.Start:
                        currentToken = ProcessStartState(out state, currentChar);
                        currentPosition++;
                        break;
                    case State.KeyWord1:
                        currentToken = ProcessKeyWordState(out state, currentChar, State.KeyWord2);
                        currentPosition++;
                        break;
                    case State.KeyWord2:
                        currentToken = ProcessKeyWordState(out state, currentChar, State.KeyWord3);
                        currentPosition++;
                        break;
                    case State.KeyWord3:
                        currentToken = ProcessKeyWordState(out state, currentChar, State.KeyWord4);
                        currentPosition++;
                        break;
                    case State.KeyWord4:
                        currentToken = ProcessKeyWordState(out state, currentChar,State.KeyWord5);
                        currentPosition++;
                        break;
                    case State.Var:
                        currentToken = ProcessVarState(out state, currentChar);
                        currentPosition++;
                        break;
                    case State.Integer:
                        currentToken = ProcessIntegerState(out state, currentChar);
                        currentPosition++;
                        break;
                    case State.Decimal:
                        currentToken = ProcessDecimalState(out state, currentChar);
                        currentPosition++;
                        break;
                    case State.String:
                        currentToken = ProcessStringState(out state, currentChar);
                        currentPosition++;
                        break;
                    case State.ClassProterty1:
                        currentToken = ProcessClassProperty1State(out state, currentChar);
                        currentPosition++;
                        break;
                    case State.ClassProterty2:
                        currentToken = ProcessClassProperty2State(out state, currentChar);
                        currentPosition++;
                        break;
                    case State.Relop:
                        currentToken = ProcessRelopState(out state, currentChar);
                        currentPosition++;
                        break;
                    case State.AddSub:
                        currentToken = ProcessAddSubState(out state, currentChar);
                        currentPosition++;
                        break;
                }
            }
            return currentToken;
        }

        private Token ProcessAddSubState(out State state, char currentChar)
        {
            Token token;
            if (currentChar == '+' || currentChar == '-')
            {
                state = State.Done;
                var readedStr = GetReadedChars();
                if (readedStr.Equals("++"))
                    token = new Token(TokenType.SelfAdd);
                else if (readedStr.Equals("--"))
                    token = new Token(TokenType.SelfSub);
                else
                    throw new WordNotSupportedException(readedStr);
            }
            else
            {
                state = State.Done;
                var readedStr = GetReadedCharsNotContainCurrent();
                token = readedStr.Equals("+") ? new Token(TokenType.Add) : new Token(TokenType.Sub);
                currentPosition--;
            }
            return token;
        }

        private Token ProcessRelopState(out State state, char currentChar)
        {
            Token token = null;
            if (currentChar == '=' || currentChar == '|' || currentChar == '&')
            {
                state = State.Done;
                var readedStr = GetReadedChars();
                switch (readedStr)
                {
                    case ">=":
                        token = new Token(TokenType.GreaterOrEqual);
                        break;
                    case "==":
                        token = new Token(TokenType.Equal);
                        break;
                    case "<=":
                        token = new Token(TokenType.LessOrEqual);
                        break;
                    case "!=":
                        token = new Token(TokenType.NotEqual);
                        break;
                    case "||":
                        token = new Token(TokenType.Or);
                        break;
                    case "&&":
                        token = new Token(TokenType.And);
                        break;
                }
            }
            else
            {
                state = State.Done;
                var readedStr = GetReadedCharsNotContainCurrent();
                switch (readedStr)
                {
                    case ">":
                        token = new Token(TokenType.Greater);
                        break;
                    case "<":
                        token = new Token(TokenType.Less);
                        break;
                    case "=":
                        token = new Token(TokenType.Assignment);
                        break;
                    case "!":
                        token = new Token(TokenType.Not);
                        break;
                }
                currentPosition--;
            }
            return token;
        }

        private Token ProcessClassProperty2State(out State state, char currentChar)
        {
            Token token = null;
            if (JackieValidator.IsLetterOrDigit(currentChar))
                state = State.ClassProterty2;
            else
            {
                state = State.Done;
                token = new Token(TokenType.ClassProperty, GetReadedCharsNotContainCurrent());
                currentPosition--;
            }
            return token;
        }

        private Token ProcessClassProperty1State(out State state, char currentChar)
        {
            Token token = null;
            if (JackieValidator.IsLetter(currentChar))
                state = State.ClassProterty2;
            else
            {
                throw new WordNotSupportedException(GetReadedChars());
            }
            return token;
        }

        private Token ProcessStringState(out State state, char currentChar)
        {
            Token token = null;
            if (currentChar != '"')
                state = State.String;
            else //if (currentChar == '"')
            {
                state = State.Done;
                token = new Token(TokenType.String, GetReadedChars());
            }
            return token;
        }

        private Token ProcessDecimalState(out State state, char currentChar)
        {
            Token token = null;
            if (JackieValidator.IsDigit(currentChar))
                state = State.Decimal;
            else
            {
                state = State.Done;
                token = new Token(TokenType.Numberic, GetReadedCharsNotContainCurrent());
                currentPosition--;
            }
            return token;
        }

        private Token ProcessIntegerState(out State state, char currentChar)
        {
            Token token = null;
            if (JackieValidator.IsDigit(currentChar))
                state = State.Integer;
            else if (currentChar == '.')
                state = State.Decimal;
            else
            {
                state = State.Done;
                token = new Token(TokenType.Numberic, GetReadedCharsNotContainCurrent());
                currentPosition--;
            }
            return token;
        }

        private Token ProcessVarState(out State state, char currentChar)
        {
            Token token = null;
            if (JackieValidator.IsLetterOrDigit(currentChar))
                state = State.Var;
            else
            {
                state = State.Done;
                token = new Token(TokenType.Id, GetReadedCharsNotContainCurrent());
                currentPosition--;
            }
            return token;
        }

        private Token ProcessKeyWordState(out State state, char currentChar, State nextState)
        {
            Token token = null;
            if (keyWordDefine.GetJudgement(nextState).Contains(currentChar.ToString()))
                state = nextState;
            else if (JackieValidator.IsLetterOrDigit(currentChar))
                state = State.Var;
            else
            {
                state = State.Done;
                var readedStr = GetReadedCharsNotContainCurrent();
                token = keyWordDefine.GetToken(readedStr);
                token = token ?? new Token(TokenType.Id, readedStr);
                currentPosition--;
            }
            return token;
        }

        private Token ProcessStartState(out State state, char currentChar)
        {
            if (keyWordDefine.GetJudgement(State.KeyWord1).Contains(currentChar.ToString()))
                state = State.KeyWord1;
            else if (JackieValidator.IsLetter(currentChar))
                state = State.Var;
            else if (JackieValidator.IsDigit(currentChar))
                state = State.Integer;
            else if (currentChar == '"')
                state = State.String;
            else if (currentChar == '.')
                state = State.ClassProterty1;
            else if (JackieValidator.IsRelationalOperator(currentChar)) //关系运算符
                state = State.Relop;
            else if (currentChar == '+' || currentChar == '-')
                state = State.AddSub;
            else if (JackieValidator.IsSpace(currentChar))
            {
                state = State.Done;
                return new Token(TokenType.Space, currentChar.ToString());
            }
            else
            {
                state = State.Done;
                switch (currentChar)
                {
                    case '*':
                        return new Token(TokenType.Mul);
                    case '/':
                        return new Token(TokenType.Div);
                    case '%':
                        return new Token(TokenType.Mod);
                    case '(':
                        return new Token(TokenType.LeftBracket);
                    case ')':
                        return new Token(TokenType.RightBracket);
                    case '{':
                        return new Token(TokenType.LeftBrace);
                    case '}':
                        return new Token(TokenType.RightBrace);
                    case ';':
                        return new Token(TokenType.StatementEndSign);
                    default:
                        throw new WordNotSupportedException(currentChar.ToString());
                }
            }
            return null;
        }
    }
}